package com.hospital.register.cmn.excel.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.hospital.register.cmn.service.DictService;
import com.hospital.register.common.exception.BusinessException;
import com.hospital.register.common.result.ResultCodeEnum;
import com.hospital.register.dto.cmn.DictExcelDto;
import com.hospital.register.model.cmn.Dict;
import com.hospital.register.constants.BaseEntityConstants;
import org.springframework.beans.BeanUtils;

import java.util.*;

/**
 * 监听读取 excel 中的数据
 *
 * @author 刘逸晖
 */
public class DictReadListener extends AnalysisEventListener<DictExcelDto> {

    /**
     * 处理字典业务
     */
    private DictService dictService;

    /**
     * 插入时，每批数据的大小
     */
    private final int batchSize;

    /**
     * 存放每批数据的容器
     */
    private List<Dict> batchDictList;

    /**
     * 每批数据的 id列表
     * 用于插入前，统一检查 id 是否重复
     */
    private List<Long> batchIdList;

    /**
     * 每批数据的父 id 列表
     * 用于插入前检查是否存在
     */
    private Set<Long> batchParentIdSet;

    /**
     * 所有字典数据的 id 列表
     * 用于出现异常后，统一删除本次请求导入的数据
     */
    private List<Long> allIdList;

    /**
     * 行号
     */
    private int lineNumber;

    public DictReadListener(DictService dictService, int batchSize) {
        this.dictService = dictService;
        this.batchSize = batchSize;
        this.batchDictList = new ArrayList<>(batchSize);
        this.batchIdList = new ArrayList<>(batchSize);
        this.batchParentIdSet = new HashSet<>(batchSize);
        this.allIdList = new LinkedList<>();
    }

    /**
     * 将读取到的数据加入批次
     * 如果批次已满，则统一插入到数据库
     *
     * @param excelData 一行数据
     * @param context
     */
    @Override
    public void invoke(DictExcelDto excelData, AnalysisContext context) {
        this.lineNumber++;
        //单个校验
        this.dictService.violatedConstraints(excelData, this.lineNumber);

        //模型转换
        Dict data = new Dict();
        Date now = new Date();
        BeanUtils.copyProperties(excelData, data);
        data.setCreateTime(now);
        data.setUpdateTime(now);
        data.setIsDeleted(BaseEntityConstants.NOT_DELETED);

        //将数据加入批次
        this.batchDictList.add(data);
        this.batchIdList.add(data.getId());

        if (!this.batchIdList.contains(data.getParentId())) {//父节点不在本批次中
            this.batchParentIdSet.add(data.getParentId());
        }

        if (this.batchDictList.size() >= this.batchSize) {//批次已满，保存
            this.saveBatchDict();
        }
    }

    /**
     * 保存一批字典
     */
    private void saveBatchDict() {
        this.allIdList.addAll(this.batchIdList);

        //统一检查是否有任一一行数据是已存在的
        boolean existed = this.dictService.existAny(this.batchIdList);
        if (existed) {
            StringBuilder message = new StringBuilder("抱歉，数据已存在。");
            message.append("范围：").append(lineNumber - this.batchDictList.size() + 1).append("，").append(this.lineNumber);
            throw new BusinessException(message.toString(), ResultCodeEnum.DICT_IS_EXISTED.getCode());
        }

        //统一检查本批数据的父节点是否都存在
        existed = this.dictService.existAll(this.batchParentIdSet);
        if (!existed) {//有不存在的
            StringBuilder message = new StringBuilder("抱歉，父节点不存在。");
            message.append("范围：").append(lineNumber - this.batchDictList.size() + 1).append("，").append(this.lineNumber);
            throw new BusinessException(message.toString(), ResultCodeEnum.DICT_IS_NOT_EXISTED.getCode());
        }

        this.dictService.insertList(this.batchDictList);
        this.batchDictList.clear();
        this.batchIdList.clear();
        this.batchParentIdSet.clear();
    }

    /**
     * 插入残余数据
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        if (this.batchDictList.size() >= 1) {//存在残余数据
            this.saveBatchDict();
        }
    }

    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        if (this.allIdList.size() > 0) {
            this.dictService.deleteList(allIdList);
        }
        throw exception;
    }

}
